import array
import codecs

from kivy.logger import Logger

from amgineirc.model.amgine.parts.spinner import Spinner
from amgineirc.model.amgine.engines.exceptions import UnableToFormHeaderException
from amgineirc.model.amgine.engines.exceptions import UnableToFormHeaderException


class Unspin(object):
    """
    istream is data from Spin.
    this reads one or more atoms from istream at a time and forms a value for the current spinner.
    it lets the spinner convert the value to a key
    it stores the keys in a list of keys.
    returns the list of keys.
   """
    def __init__(self, rule, istream):
        self._rule = rule
        self._i_stream = istream
        self._key_list = []
        self._build_spinners()
        self._spinner_index = 0

    def unspin(self):
        self._init_spinners()
        self._do_header()
        k = self._next_key()
        while k is not None and k != self._rule.eot_key:
            self._key_list.append(k)
            # continue processing the code file
            k = self._next_key()
        return self._key_list[:]

    def _init_spinners(self):
        self._spinner_index = 0
        # reset the spinners for a new shift
        for spinner in self._spinners:
            spinner.reset()

    def _do_header(self):
        # header noise
        for i in range(0, self._rule.spin_noise_count, 1):
            self._i_stream.read_one()
        # read the value for the shifts
        v = self._read_value()
        if v is not None:
            shift = self._spinners[0].peek_value_index(v)
            if shift is not None:
                if shift > 0:
                    # shift the spinners
                    for spinner in self._spinners:
                        spinner.shift(shift)
        # read the value of start spinner
        v = self._read_value()
        if v is not None:
            index = self._spinners[0].peek_value_index(v)
            if index > 0:
                self._spinner_index = index % len(self._spinners)
            return
        # error
        raise UnableToFormHeaderException()

    def _read_value(self):
        v_len = self._spinners[self._spinner_index].get_value_length()
        v = self._spinners[self._spinner_index]._values[0]
        if v_len == 1:
            # return a single byte or char
            return self._i_stream.read_one()
        else:
            # return a string of chars
            l = []
            for i in range(v_len):
                atom = self._i_stream.read_one()
                if atom is None:
                    return None
                else:
                    l.append(atom)
            return ''.join(l)

    def _next_key(self):
        """reads a value from the code file
        and returns the cooresponding key from the spinner.
        """
        v = self._read_value()
        if v is not None:
            k = self._spinners[self._spinner_index].get_key(v)
            if k is None:
                    raise LookupError("Unspin Lookup Error: Unable to find the value {0} in the current spinner.".format(v))
            else:
                # the spinner found a match and returned the value's key
                self._inc_spinner_index()
            return k
        else:
            # some input is missing
            return None

    def _build_spinners(self):
        """build spinners according to the rules"""
        self._spinners = []
        lenK = len(self._rule.keys)
        lenV = len(self._rule.values)
        if lenK == lenV:
            for i in range(0, lenK, 1):
                if len(self._rule.keys[i]) == len(self._rule.values[i]):
                    self._spinners.append(
                        Spinner(
                            self._rule.keys[i],
                            self._rule.values[i],
                            self._rule.shifts[i]
                            )
                        )

    def _inc_spinner_index(self):
        # rotate to the next spinner for the next time
        self._spinner_index += 1
        if self._spinner_index == len(self._spinners):
            self._spinner_index = 0
